/*
 * Copyright (C) 2012 Sony Network Entertainment Intl. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY SONY NETWORK ENTERTAINMENT INTL. ``AS IS''
 * ANY ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SONY NETWORK ENTERTAINMENT INTL.
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include "config.h"

#if ENABLE(INSPECTOR_SERVER)
#include "WebInspectorServer.h"

#include "FileSystem.h"
#include "InspectorProtocolVersion.h"
#include "InspectorValues.h"
#include "MIMETypeRegistry.h"
#include "NotImplemented.h"
#include "WebContext.h"
#include "WebInspectorProxy.h"
#include "WebKitVersion.h"
#include "WebProcessProxy.h"

#include <errno.h>
#include <manx/NetworkProfile.h>
#include <wtf/Assertions.h>
#include <wtf/text/CString.h>

using namespace WebCore;

namespace {
    // Use the last revision before the Blink/WebKit fork as our reference revision
    // (cf. https://groups.google.com/a/chromium.org/d/msg/blink-dev/J41PSKuMan0/gD5xcqicqP8J)
    // Revision numbering was maintained in Blink, but since history has started diverging after
    // that commit the WebKit revisions aren't mappable anymore.
    // ChromeDriver adjusts some event handling depending on that revision number.
    // (grep 'blink_revision' in chromedriver/chrome/navigation_tracker.cc)
    const int webkitRevision = 147503;
}

namespace WebKit {

bool WebInspectorServer::platformResourceForPath(const String& path, Vector<char>& data, String& contentType)
{
    if (!path.startsWith('/'))
        return false;

    if (m_clientMap.isEmpty())
        return false;

    if (path.startsWith("/json")) {
        const String jsonSubpath = path.substring(5);

        if (jsonSubpath.isEmpty()) {
            RefPtr<InspectorArray> message = InspectorArray::create();

            for (HashMap<unsigned, WebInspectorProxy*>::const_iterator it = m_clientMap.begin(); it != m_clientMap.end(); ++it) {
                WebInspectorProxy* client = it->value;
                WebPageProxy* page = client->page();
                WebProcessProxy* process = page->process();
                WebContext* context = process->context();
                RefPtr<InspectorObject> tabInfo = InspectorObject::create();

                tabInfo->setString("id", String::number(it->key));
                tabInfo->setNumber("processID", process->processIdentifier());
                tabInfo->setNumber("processPageID", page->pageID());
                tabInfo->setString("title", page->pageTitle());
                tabInfo->setString("type", "page");
                tabInfo->setString("url", page->activeURL());
                tabInfo->setString("webSocketDebuggerUrl", String::format("ws://%s:%u/devtools/page/%u", bindAddress().latin1().data(), port(), it->key));

                if (context->webProcessPath().endsWith("SecureWebProcess.self"))
                    tabInfo->setString("processDisplayName", "Live Area / Store / RegCAM");
                else if (context->webProcessPath().endsWith("WebProcessHTMLTile.self"))
                    tabInfo->setString("processDisplayName", "HTML Live Tiles");
                else
                    tabInfo->setString("processDisplayName", context->userStorageDirectory().endsWith("webbrowser") ? "Web Browser" : "Miscellaneous");
                message->pushObject(tabInfo);
            }

            CString tabInfo = message->toJSONString().utf8();
            data.append(tabInfo.data(), tabInfo.length());
        } else if (jsonSubpath == "/version") {
            // Basic version info needed by the WebDriver server
            RefPtr<InspectorObject> versionInfo = InspectorObject::create();
            versionInfo->setString("Browser", ""); // Intentionally kept blank, indicates custom browser to ChromeDriver
            versionInfo->setString("Protocol-Version", inspectorProtocolVersion());
            versionInfo->setString("User-Agent", WebPageProxy::standardUserAgent());
            versionInfo->setString("WebKit-Version", String::format("%d.%d (@%d)", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, webkitRevision));

            CString versionInfoString = versionInfo->toJSONString().utf8();
            data.append(versionInfoString.data(), versionInfoString.length());
        } else {
            // Unsupported command
            return false;
        }

        contentType = "application/json; charset=utf-8";
        return true;
    }

    WebInspectorProxy* inspector = m_clientMap.begin()->value;
    ASSERT(inspector);

    // Point the default path to display the inspector landing page.
    // All other paths are mapped directly to a resource, if possible.
    const KURL baseURL(KURL(), inspector->inspectorBaseURL());
    const KURL resourceURL(baseURL, (path == "/") ? inspector->inspectorPageURL() : path.substring(1));

    if (!resourceURL.isLocalFile()) {
        notImplemented();
        return false;
    }

    const String localPath = resourceURL.fileSystemPath();

    // Detect attempts to access files outside the inspector folder (ie. /../../some/path)
    if (!localPath.startsWith(baseURL.fileSystemPath())) {
        LOG_ERROR("WebInspectorServer: detected illegal path '%s'!", localPath.utf8().data());
        return false;
    }

    PlatformFileHandle handle = openFile(localPath, OpenForRead);
    if (!isHandleValid(handle)) {
        LOG_ERROR("WebInspectorServer: couldn't access platform resource '%s' for reading! (%d)", localPath.utf8().data(), errno);
        return false;
    }

    long long fileSize;
    if (!getFileSize(localPath, fileSize)) {
        LOG_ERROR("WebInspectorServer: couldn't get file size for '%s'! (%d)", localPath.utf8().data(), errno);
        closeFile(handle);
        return false;
    }
    // NB: grow takes a size_t arg, so fileSize will be truncated to size_t which is an unsigned int.
    // This is safe because if we ever have fileSize > UINT_MAX the check below will simply fail.
    // This would only happen if we had to handle files >4GB, which we don't have in the inspector anyway.
    data.grow(fileSize);
    if (readFromFile(handle, data.data(), data.size()) < fileSize) {
        LOG_ERROR("WebInspectorServer: didn't read all contents of file '%s'! (%d)", localPath.utf8().data(), errno);
        closeFile(handle);
        return false;
    }
    closeFile(handle);

    contentType = MIMETypeRegistry::getMIMETypeForPath(localPath);

    return true;
}

void WebInspectorServer::updateServerState()
{
    char* envValue = getenv("MANX_INSPECTOR_SERVER_PORT");
    if (!envValue)
        return;

    if (!m_clientMap.isEmpty() && serverState() == Closed) {
        const int inspectorServerPort = atoi(envValue);
        if (inspectorServerPort <= 0) {
            LOG_ERROR("Invalid Inspector Server port!");
            return;
        }

        if (!listen(Manx::NetworkProfile::ipAddress(), inspectorServerPort))
            LOG_ERROR("Couldn't start the Inspector Server!");

    } else if (m_clientMap.isEmpty() && serverState() == Listening) {
        close();
        ASSERT(serverState() == Closed);
    }
}

}

#endif // ENABLE(INSPECTOR_SERVER)
